/*
 *  Copyright (c) 2018, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @file
 *   This file includes definitions for the spinel based radio transceiver.
 */

#ifndef RADIO_SPINEL_HPP_
#define RADIO_SPINEL_HPP_

#include <openthread/platform/diag.h>
#include <openthread/platform/radio.h>

#include "openthread-spinel-config.h"
#include "core/radio/max_power_table.hpp"
#include "lib/spinel/logger.hpp"
#include "lib/spinel/radio_spinel_metrics.h"
#include "lib/spinel/spinel.h"
#include "lib/spinel/spinel_driver.hpp"
#include "lib/spinel/spinel_interface.hpp"
#include "ncp/ncp_config.h"

namespace ot {
namespace Spinel {

struct RadioSpinelCallbacks
{
    /**
     * This callback notifies user of `RadioSpinel` of a received frame.
     *
     * @param[in]  aInstance  The OpenThread instance structure.
     * @param[in]  aFrame     A pointer to the received frame or nullptr if the receive operation failed.
     * @param[in]  aError     kErrorNone when successfully received a frame,
     *                        kErrorAbort when reception was aborted and a frame was not received,
     *                        kErrorNoBufs when a frame could not be received due to lack of rx buffer space.
     */
    void (*mReceiveDone)(otInstance *aInstance, otRadioFrame *aFrame, Error aError);

    /**
     * The callback notifies user of `RadioSpinel` that the transmit operation has completed, providing, if
     * applicable, the received ACK frame.
     *
     * @param[in]  aInstance  The OpenThread instance structure.
     * @param[in]  aFrame     The transmitted frame.
     * @param[in]  aAckFrame  A pointer to the ACK frame, nullptr if no ACK was received.
     * @param[in]  aError     kErrorNone when the frame was transmitted,
     *                        kErrorNoAck when the frame was transmitted but no ACK was received,
     *                        kErrorChannelAccessFailure tx failed due to activity on the channel,
     *                        kErrorAbort when transmission was aborted for other reasons.
     */
    void (*mTransmitDone)(otInstance *aInstance, otRadioFrame *aFrame, otRadioFrame *aAckFrame, Error aError);

    /**
     * This callback notifies user of `RadioSpinel` that energy scan is complete.
     *
     * @param[in]  aInstance  The OpenThread instance structure.
     * @param[in]  aMaxRssi   Maximum RSSI seen on the channel, or `SubMac::kInvalidRssiValue` if failed.
     */
    void (*mEnergyScanDone)(otInstance *aInstance, int8_t aMaxRssi);

    /**
     * This callback notifies user of `RadioSpinel` that the bus latency has been changed.
     *
     * @param[in]  aInstance  The OpenThread instance structure.
     */
    void (*mBusLatencyChanged)(otInstance *aInstance);

    /**
     * This callback notifies user of `RadioSpinel` that the transmission has started.
     *
     * @param[in]  aInstance  A pointer to the OpenThread instance structure.
     * @param[in]  aFrame     A pointer to the frame that is being transmitted.
     */
    void (*mTxStarted)(otInstance *aInstance, otRadioFrame *aFrame);

    /**
     * This callback notifies user of `RadioSpinel` that the radio interface switchover has completed.
     *
     * @param[in]  aInstance  A pointer to the OpenThread instance structure.
     * @param[in]  aSuccess   A value indicating if the switchover was successful or not.
     */
    void (*mSwitchoverDone)(otInstance *aInstance, bool aSuccess);

    /**
     * This method saves the radio spinel metrics to the temporary storage.
     *
     * @param[in]  aMetrics   A reference to the radio spinel metrics.
     * @param[in]  aContext   A pointer to application-specific context.
     */
    void (*mSaveRadioSpinelMetrics)(const otRadioSpinelMetrics &aMetrics, void *aContext);

    /**
     * This method restores the radio spinel metrics from the temporary storage.
     *
     * @param[out] aMetrics   A reference to the radio spinel metrics.
     * @param[in]  aContext   A pointer to application-specific context.
     */
    otError (*mRestoreRadioSpinelMetrics)(otRadioSpinelMetrics &aMetrics, void *aContext);

    /**
     * The pointer to application-specific context for methods `mSaveRadioSpinelMetrics()` and
     * `mRestoreRadioSpinelMetrics()`.
     */
    void *mRadioSpinelMetricsContext;
};

/**
 * The class for providing a OpenThread radio interface by talking with a radio-only
 * co-processor(RCP).
 */
class RadioSpinel : private Logger
{
public:
    /**
     * Initializes the spinel based OpenThread transceiver.
     */
    RadioSpinel(void);

    /**
     * Deinitializes the spinel based OpenThread transceiver.
     */
    ~RadioSpinel(void) { Deinit(); }

    /**
     * Initialize this radio transceiver.
     *
     * @param[in]  aSkipRcpVersionCheck        TRUE to skip RCP version check, FALSE to perform the check.
     * @param[in]  aSoftwareReset              When doing RCP recovery, TRUE to try software reset first, FALSE to
     *                                         directly do a hardware reset.
     * @param[in]  aSpinelDriver               A pointer to the spinel driver instance that this object depends on.
     * @param[in]  aRequiredRadioCaps          The required radio capabilities. RadioSpinel will check if RCP has
     *                                         the required capabilities during initialization.
     * @param[in]  aEnableRcpTimeSync          TRUE to enable RCP time sync, FALSE to not enable.
     */
    void Init(bool          aSkipRcpVersionCheck,
              bool          aSoftwareReset,
              SpinelDriver *aSpinelDriver,
              otRadioCaps   aRequiredRadioCaps,
              bool          aEnableRcpTimeSync);

    /**
     * This method sets the notification callbacks.
     *
     * @param[in]  aCallbacks  A pointer to structure with notification callbacks.
     */
    void SetCallbacks(const struct RadioSpinelCallbacks &aCallbacks);

    /**
     * Deinitialize this radio transceiver.
     */
    void Deinit(void);

    /**
     * Gets the status of promiscuous mode.
     *
     * @retval true   Promiscuous mode is enabled.
     * @retval false  Promiscuous mode is disabled.
     */
    bool IsPromiscuous(void) const { return mIsPromiscuous; }

    /**
     * Sets the status of promiscuous mode.
     *
     * @param[in]   aEnable     Whether to enable or disable promiscuous mode.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError SetPromiscuous(bool aEnable);

    /**
     * Sets the status of RxOnWhenIdle mode.
     *
     * @param[in]   aEnable     Whether to enable or disable RxOnWhenIdle mode.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError SetRxOnWhenIdle(bool aEnable);

    /**
     * Sets the Short Address for address filtering.
     *
     * @param[in] aShortAddress  The IEEE 802.15.4 Short Address.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError SetShortAddress(uint16_t aAddress);

    /**
     * Sets the alternate short address.
     *
     * @param[in] aShortAddress   The alternate short address.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError SetAlternateShortAddress(uint16_t aAddress);

    /**
     * Gets the factory-assigned IEEE EUI-64 for this transceiver.
     *
     * @param[in]  aInstance   The OpenThread instance structure.
     * @param[out] aIeeeEui64  A pointer to the factory-assigned IEEE EUI-64.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError GetIeeeEui64(uint8_t *aIeeeEui64);

    /**
     * Sets the Extended Address for address filtering.
     *
     * @param[in] aExtAddress  A pointer to the IEEE 802.15.4 Extended Address stored in little-endian byte order.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError SetExtendedAddress(const otExtAddress &aExtAddress);

    /**
     * Sets the PAN ID for address filtering.
     *
     * @param[in]   aPanId  The IEEE 802.15.4 PAN ID.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError SetPanId(uint16_t aPanId);

    /**
     * Gets the radio's transmit power in dBm.
     *
     * @param[out]  aPower    The transmit power in dBm.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError GetTransmitPower(int8_t &aPower);

    /**
     * Sets the radio's transmit power in dBm.
     *
     * @param[in]   aPower     The transmit power in dBm.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError SetTransmitPower(int8_t aPower);

    /**
     * Gets the radio's CCA ED threshold in dBm.
     *
     * @param[out]  aThreshold    The CCA ED threshold in dBm.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError GetCcaEnergyDetectThreshold(int8_t &aThreshold);

    /**
     * Sets the radio's CCA ED threshold in dBm.
     *
     * @param[in]   aThreshold     The CCA ED threshold in dBm.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError SetCcaEnergyDetectThreshold(int8_t aThreshold);

    /**
     * Gets the FEM's Rx LNA gain in dBm.
     *
     * @param[out]  aGain    The FEM's Rx LNA gain in dBm.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError GetFemLnaGain(int8_t &aGain);

    /**
     * Sets the FEM's Rx LNA gain in dBm.
     *
     * @param[in]   aGain     The FEM's Rx LNA gain in dBm.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError SetFemLnaGain(int8_t aGain);

    /**
     * Returns the radio capabilities.
     *
     * @returns The radio capability bit vector.
     */
    otRadioCaps GetRadioCaps(void) const { return sRadioCaps; }

    /**
     * Gets the most recent RSSI measurement.
     *
     * @returns The RSSI in dBm when it is valid.  127 when RSSI is invalid.
     */
    int8_t GetRssi(void);

    /**
     * Returns the radio receive sensitivity value.
     *
     * @returns The radio receive sensitivity value in dBm.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    int8_t GetReceiveSensitivity(void) const { return mRxSensitivity; }

    /**
     * Gets current state of the radio.
     *
     * @return  Current state of the radio.
     */
    otRadioState GetState(void) const;

    /**
     * Gets the current receiving channel.
     *
     * @returns Current receiving channel.
     */
    uint8_t GetChannel(void) const { return mChannel; }

#if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE
    /**
     * Enable the radio coex.
     *
     * @param[in] aInstance  The OpenThread instance structure.
     * @param[in] aEnabled   TRUE to enable the radio coex, FALSE otherwise.
     *
     * @retval OT_ERROR_NONE     Successfully enabled.
     * @retval OT_ERROR_FAILED   The radio coex could not be enabled.
     */
    otError SetCoexEnabled(bool aEnabled);

    /**
     * Check whether radio coex is enabled or not.
     *
     * @param[in] aInstance  The OpenThread instance structure.
     *
     * @returns TRUE if the radio coex is enabled, FALSE otherwise.
     */
    bool IsCoexEnabled(void);

    /**
     * Retrieves the radio coexistence metrics.
     *
     * @param[out] aCoexMetrics  A reference to the coexistence metrics structure.
     *
     * @retval OT_ERROR_NONE          Successfully retrieved the coex metrics.
     * @retval OT_ERROR_INVALID_ARGS  @p aCoexMetrics was nullptr.
     */
    otError GetCoexMetrics(otRadioCoexMetrics &aCoexMetrics);
#endif // OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE

    /**
     * Get currently active interface.
     *
     * @param[out] aIid IID of the interface that owns the radio.
     *
     * @retval  OT_ERROR_NONE               Successfully got the property.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     * @retval  OT_ERROR_NOT_IMPLEMENTED    Failed due to lack of the support in radio
     * @retval  OT_ERROR_INVALID_COMMAND    Platform supports all interfaces simultaneously.
     *                                      (i.e. no active/inactive interface concept in the platform level)
     */
    otError GetMultipanActiveInterface(spinel_iid_t *aIid);

    /**
     * Sets specified radio interface active
     *
     * This function allows selecting currently active radio interface on platforms that do not support parallel
     * communication on multiple interfaces. I.e. if more than one interface is in receive state calling
     * SetMultipanActiveInterface guarantees that specified interface will not be losing frames. This function
     * returns if the request was received properly. After interface switching is complete SwitchoverDone callback is
     * Invoked. Switching interfaces may take longer if aCompletePending is set true.
     *
     * @param[in] aIid              IID of the interface to set active.
     * @param[in] aCompletePending  Set true if pending radio operation should complete first(Soft switch) or false if
     * ongoing operations should be interrupted (Force switch).
     *
     * @retval  OT_ERROR_NONE               Successfully requested interface switch.
     * @retval  OT_ERROR_BUSY               Failed due to another operation on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     * @retval  OT_ERROR_NOT_IMPLEMENTED    Failed due to lack of support in radio for the given interface id or
     * @retval  OT_ERROR_INVALID_COMMAND    Platform supports all interfaces simultaneously
     *                                      (i.e. no active/inactive interface concept in the platform level)
     * @retval  OT_ERROR_ALREADY            Given interface is already active.
     */
    otError SetMultipanActiveInterface(spinel_iid_t aIid, bool aCompletePending);

    /**
     * Returns a reference to the transmit buffer.
     *
     * The caller forms the IEEE 802.15.4 frame in this buffer then calls otPlatRadioTransmit() to request transmission.
     *
     * @returns A reference to the transmit buffer.
     */
    otRadioFrame &GetTransmitFrame(void) { return mTxRadioFrame; }

    /**
     * Enables or disables source address match feature.
     *
     * @param[in]  aEnable     Enable/disable source address match feature.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError EnableSrcMatch(bool aEnable);

    /**
     * Adds a short address to the source address match table.
     *
     * @param[in]  aInstance      The OpenThread instance structure.
     * @param[in]  aShortAddress  The short address to be added.
     *
     * @retval  OT_ERROR_NONE               Successfully added short address to the source match table.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     * @retval  OT_ERROR_NO_BUFS            No available entry in the source match table.
     */
    otError AddSrcMatchShortEntry(uint16_t aShortAddress);

    /**
     * Removes a short address from the source address match table.
     *
     * @param[in]  aInstance      The OpenThread instance structure.
     * @param[in]  aShortAddress  The short address to be removed.
     *
     * @retval  OT_ERROR_NONE               Successfully removed short address from the source match table.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     * @retval  OT_ERROR_NO_ADDRESS         The short address is not in source address match table.
     */
    otError ClearSrcMatchShortEntry(uint16_t aShortAddress);

    /**
     * Clear all short addresses from the source address match table.
     *
     * @param[in]  aInstance   The OpenThread instance structure.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError ClearSrcMatchShortEntries(void);

    /**
     * Add an extended address to the source address match table.
     *
     * @param[in]  aInstance    The OpenThread instance structure.
     * @param[in]  aExtAddress  The extended address to be added stored in little-endian byte order.
     *
     * @retval  OT_ERROR_NONE               Successfully added extended address to the source match table.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     * @retval  OT_ERROR_NO_BUFS            No available entry in the source match table.
     */
    otError AddSrcMatchExtEntry(const otExtAddress &aExtAddress);

    /**
     * Remove an extended address from the source address match table.
     *
     * @param[in]  aInstance    The OpenThread instance structure.
     * @param[in]  aExtAddress  The extended address to be removed stored in little-endian byte order.
     *
     * @retval  OT_ERROR_NONE               Successfully removed the extended address from the source match table.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     * @retval  OT_ERROR_NO_ADDRESS         The extended address is not in source address match table.
     */
    otError ClearSrcMatchExtEntry(const otExtAddress &aExtAddress);

    /**
     * Clear all the extended/long addresses from source address match table.
     *
     * @param[in]  aInstance   The OpenThread instance structure.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError ClearSrcMatchExtEntries(void);

    /**
     * Begins the energy scan sequence on the radio.
     *
     * @param[in]  aScanChannel     The channel to perform the energy scan on.
     * @param[in]  aScanDuration    The duration, in milliseconds, for the channel to be scanned.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration);

    /**
     * Switches the radio state from Receive to Transmit.
     *
     * @param[in] aFrame     A reference to the transmitted frame.
     *
     * @retval  OT_ERROR_NONE               Successfully transitioned to Transmit.
     * @retval  OT_ERROR_BUSY               Failed due to another transmission is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     * @retval  OT_ERROR_INVALID_STATE      The radio was not in the Receive state.
     */
    otError Transmit(otRadioFrame &aFrame);

    /**
     * Switches the radio state from Sleep to Receive.
     *
     * @param[in]  aChannel   The channel to use for receiving.
     *
     * @retval OT_ERROR_NONE          Successfully transitioned to Receive.
     * @retval OT_ERROR_INVALID_STATE The radio was disabled or transmitting.
     */
    otError Receive(uint8_t aChannel);

    /**
     * Schedule a radio reception window at a specific time and duration.
     *
     * @param[in]  aWhen      The receive window start time in the local
     *                        radio clock, see `otPlatRadioGetNow`. The radio
     *                        receiver SHALL be on and ready to receive the first
     *                        symbol of a frame's SHR at the window start time.
     * @param[in]  aDuration  The receive window duration, in microseconds, as
     *                        measured by the local radio clock.
     * @param[in]  aChannel   The channel to use for receiving.
     *
     * @retval OT_ERROR_NONE          Successfully scheduled the reception.
     * @retval OT_ERROR_INVALID_STATE The radio was disabled.
     */
    otError ReceiveAt(uint64_t aWhen, uint32_t aDuration, uint8_t aChannel);

    /**
     * Switches the radio state from Receive to Sleep.
     *
     * @retval OT_ERROR_NONE          Successfully transitioned to Sleep.
     * @retval OT_ERROR_BUSY          The radio was transmitting
     * @retval OT_ERROR_INVALID_STATE The radio was disabled
     */
    otError Sleep(void);

    /**
     * Enable the radio.
     *
     * @param[in]   aInstance   A pointer to the OpenThread instance.
     *
     * @retval OT_ERROR_NONE     Successfully enabled.
     * @retval OT_ERROR_FAILED   The radio could not be enabled.
     */
    otError Enable(otInstance *aInstance);

    /**
     * Disable the radio.
     *
     * @retval  OT_ERROR_NONE               Successfully transitioned to Disabled.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError Disable(void);

    /**
     * Checks whether radio is enabled or not.
     *
     * @returns TRUE if the radio is enabled, FALSE otherwise.
     */
    bool IsEnabled(void) const { return mState != kStateDisabled; }

    /**
     * Indicates whether there is a pending transmission.
     *
     * @retval TRUE  There is a pending transmission.
     * @retval FALSE There is no pending transmission.
     */
    bool IsTransmitting(void) const { return mState == kStateTransmitting; }

    /**
     * Indicates whether a transmit has just finished.
     *
     * @retval TRUE  The transmission is done.
     * @retval FALSE The transmission is not done.
     */
    bool IsTransmitDone(void) const { return mState == kStateTransmitDone; }

    /**
     * Returns the timeout timepoint for the pending transmission.
     *
     * @returns The timeout timepoint for the pending transmission.
     */
    uint64_t GetTxRadioEndUs(void) const { return mTxRadioEndUs; }

    /**
     * Processes any pending the I/O data.
     *
     * @param[in]  aContext   The process context.
     */
    void Process(const void *aContext);

#if OPENTHREAD_CONFIG_DIAG_ENABLE
    /**
     * Enables/disables the factory diagnostics mode.
     *
     * @param[in]  aMode  TRUE to enable diagnostics mode, FALSE otherwise.
     */
    void SetDiagEnabled(bool aMode) { mDiagMode = aMode; }

    /**
     * Indicates whether or not factory diagnostics mode is enabled.
     *
     * @returns TRUE if factory diagnostics mode is enabled, FALSE otherwise.
     */
    bool IsDiagEnabled(void) const { return mDiagMode; }

    /**
     * Processes RadioSpinel - specific diagnostics commands.
     *
     * @param[in]   aArgsLength     The number of arguments in @p aArgs.
     * @param[in]   aArgs           The arguments of diagnostics command line.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_INVALID_ARGS       Failed due to invalid arguments provided.
     */
    otError RadioSpinelDiagProcess(char *aArgs[], uint8_t aArgsLength);

    /**
     * Processes platform diagnostics commands.
     *
     * @param[in]   aString         A null-terminated input string.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError PlatDiagProcess(const char *aString);

    /**
     * Sets the diag output callback.
     *
     * @param[in]  aCallback   A pointer to a function that is called on outputting diag messages.
     * @param[in]  aContext    A pointer to the user context.
     */
    void SetDiagOutputCallback(otPlatDiagOutputCallback aCallback, void *aContext);

    /**
     * Gets the diag output callback.
     *
     * @param[out]  aCallback   A reference to a function that is called on outputting diag messages.
     * @param[out]  aContext    A reference to the user context.
     */
    void GetDiagOutputCallback(otPlatDiagOutputCallback &aCallback, void *&aContext);
#endif

    /**
     * Returns the radio channel mask.
     *
     * @param[in]   aPreferred  TRUE to get preferred channel mask, FALSE to get supported channel mask.
     *
     * @returns The radio channel mask according to @aPreferred:
     *   The radio supported channel mask that the device is allowed to be on.
     *   The radio preferred channel mask that the device prefers to form on.
     */
    uint32_t GetRadioChannelMask(bool aPreferred);

    /**
     * Sets MAC key and key index to RCP.
     *
     * @param[in] aKeyIdMode  The key ID mode.
     * @param[in] aKeyId      The key index.
     * @param[in] aPrevKey    Pointer to previous MAC key.
     * @param[in] aCurrKey    Pointer to current MAC key.
     * @param[in] aNextKey    Pointer to next MAC key.
     *
     * @retval  OT_ERROR_NONE               Succeeded.
     * @retval  OT_ERROR_INVALID_ARGS       One of the keys passed is invalid..
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError SetMacKey(uint8_t                 aKeyIdMode,
                      uint8_t                 aKeyId,
                      const otMacKeyMaterial *aPrevKey,
                      const otMacKeyMaterial *aCurrKey,
                      const otMacKeyMaterial *aNextKey);

    /**
     * Sets the current MAC Frame Counter value.
     *
     * @param[in] aMacFrameCounter  The MAC Frame Counter value.
     * @param[in] aSetIfLarger      If `true`, set only if the new value is larger than the current value.
     *                              If `false`, set the new value independent of the current value.
     */
    otError SetMacFrameCounter(uint32_t aMacFrameCounter, bool aSetIfLarger);

    /**
     * Sets the radio region code.
     *
     * @param[in]   aRegionCode  The radio region code.
     *
     * @retval  OT_ERROR_NONE             Successfully set region code.
     * @retval  OT_ERROR_FAILED           Other platform specific errors.
     */
    otError SetRadioRegion(uint16_t aRegionCode);

    /**
     * Gets the radio region code.
     *
     * @param[out]   aRegionCode  The radio region code.
     *
     * @retval  OT_ERROR_INVALID_ARGS     @p aRegionCode is nullptr.
     * @retval  OT_ERROR_NONE             Successfully got region code.
     * @retval  OT_ERROR_FAILED           Other platform specific errors.
     */
    otError GetRadioRegion(uint16_t *aRegionCode);

#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
    /**
     * Enable/disable or update Enhanced-ACK Based Probing in radio for a specific Initiator.
     *
     * After Enhanced-ACK Based Probing is configured by a specific Probing Initiator, the Enhanced-ACK sent to that
     * node should include Vendor-Specific IE containing Link Metrics data. This method informs the radio to start/stop
     * to collect Link Metrics data and include Vendor-Specific IE that containing the data in Enhanced-ACK sent to that
     * Probing Initiator.
     *
     * @param[in]  aLinkMetrics   This parameter specifies what metrics to query. Per spec 4.11.3.4.4.6, at most 2
     *                            metrics can be specified. The probing would be disabled if @p aLinkMetrics is
     *                            bitwise 0.
     * @param[in]  aShortAddress  The short address of the Probing Initiator.
     * @param[in]  aExtAddress    The extended source address of the Probing Initiator. @p aExtAddress MUST NOT be
     *                            nullptr.
     *
     * @retval  OT_ERROR_NONE            Successfully configured the Enhanced-ACK Based Probing.
     * @retval  OT_ERROR_INVALID_ARGS    @p aExtAddress is nullptr.
     * @retval  OT_ERROR_NOT_FOUND       The Initiator indicated by @p aShortAddress is not found when trying to clear.
     * @retval  OT_ERROR_NO_BUFS         No more Initiator can be supported.
     */
    otError ConfigureEnhAckProbing(otLinkMetrics         aLinkMetrics,
                                   const otShortAddress &aShortAddress,
                                   const otExtAddress   &aExtAddress);
#endif

#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
    /**
     * Get the current accuracy, in units of ± ppm, of the clock used for scheduling CSL operations.
     *
     * @note Platforms may optimize this value based on operational conditions (i.e.: temperature).
     *
     * @retval   The current CSL rx/tx scheduling drift, in units of ± ppm.
     */
    uint8_t GetCslAccuracy(void);
#endif

#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
    /**
     * Get the current uncertainty, in units of 10 us, of the clock used for scheduling CSL operations.
     *
     * @retval  The current CSL Clock Uncertainty in units of 10 us.
     */
    uint8_t GetCslUncertainty(void);
#endif

    /**
     * Checks whether there is pending frame in the buffer.
     *
     * @returns Whether there is pending frame in the buffer.
     */
    bool HasPendingFrame(void) const { return mSpinelDriver->HasPendingFrame(); }

    /**
     * Returns the next timepoint to recalculate RCP time offset.
     *
     * @returns The timepoint to start the recalculation of RCP time offset.
     */
    uint64_t GetNextRadioTimeRecalcStart(void) const { return mRadioTimeRecalcStart; }

    /**
     * Gets the current estimated time on RCP.
     *
     * @returns The current estimated RCP time in microseconds.
     */
    uint64_t GetNow(void) const;

    /**
     * Returns the bus speed between the host and the radio.
     *
     * @returns   bus speed in bits/second.
     */
    uint32_t GetBusSpeed(void) const;

    /**
     * Returns the bus latency between the host and the radio.
     *
     * @returns   Bus latency in microseconds.
     */
    uint32_t GetBusLatency(void) const;

    /**
     * Sets the bus latency between the host and the radio.
     *
     * @param[in]   aBusLatency  Bus latency in microseconds.
     */
    void SetBusLatency(uint32_t aBusLatency);

    /**
     * Returns the co-processor sw version string.
     *
     * @returns A pointer to the co-processor version string.
     */
    const char *GetVersion(void) const { return mSpinelDriver->GetVersion(); }

    /**
     * Sets the max transmit power.
     *
     * @param[in] aChannel    The radio channel.
     * @param[in] aMaxPower   The max transmit power in dBm.
     *
     * @retval  OT_ERROR_NONE           Successfully set the max transmit power.
     * @retval  OT_ERROR_INVALID_ARGS   Channel is not in valid range.
     */
    otError SetChannelMaxTransmitPower(uint8_t aChannel, int8_t aMaxPower);

    /**
     * Tries to retrieve a spinel property from OpenThread transceiver.
     *
     * @param[in]   aKey        Spinel property key.
     * @param[in]   aFormat     Spinel formatter to unpack property value.
     * @param[out]  ...         Variable arguments list.
     *
     * @retval  OT_ERROR_NONE               Successfully got the property.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError Get(spinel_prop_key_t aKey, const char *aFormat, ...);

    /**
     * Tries to retrieve a spinel property from OpenThread transceiver with parameter appended.
     *
     * @param[in]   aKey        Spinel property key.
     * @param[in]   aParam      Parameter appended to spinel command.
     * @param[in]   aParamSize  Size of parameter appended to spinel command
     * @param[in]   aFormat     Spinel formatter to unpack property value.
     * @param[out]  ...         Variable arguments list.
     *
     * @retval  OT_ERROR_NONE               Successfully got the property.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError GetWithParam(spinel_prop_key_t aKey,
                         const uint8_t    *aParam,
                         spinel_size_t     aParamSize,
                         const char       *aFormat,
                         ...);

    /**
     * Tries to update a spinel property of OpenThread transceiver.
     *
     * @param[in]   aKey        Spinel property key.
     * @param[in]   aFormat     Spinel formatter to pack property value.
     * @param[in]   ...         Variable arguments list.
     *
     * @retval  OT_ERROR_NONE               Successfully set the property.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError Set(spinel_prop_key_t aKey, const char *aFormat, ...);

    /**
     * Tries to insert a item into a spinel list property of OpenThread transceiver.
     *
     * @param[in]   aKey        Spinel property key.
     * @param[in]   aFormat     Spinel formatter to pack the item.
     * @param[in]   ...         Variable arguments list.
     *
     * @retval  OT_ERROR_NONE               Successfully insert item into the property.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError Insert(spinel_prop_key_t aKey, const char *aFormat, ...);

    /**
     * Tries to remove a item from a spinel list property of OpenThread transceiver.
     *
     * @param[in]   aKey        Spinel property key.
     * @param[in]   aFormat     Spinel formatter to pack the item.
     * @param[in]   ...         Variable arguments list.
     *
     * @retval  OT_ERROR_NONE               Successfully removed item from the property.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT   Failed due to no response received from the transceiver.
     */
    otError Remove(spinel_prop_key_t aKey, const char *aFormat, ...);

    /**
     * Sends a reset command to the RCP.
     *
     * @param[in] aResetType The reset type, SPINEL_RESET_PLATFORM, SPINEL_RESET_STACK, or SPINEL_RESET_BOOTLOADER.
     *
     * @retval  OT_ERROR_NONE               Successfully sent the reset command.
     * @retval  OT_ERROR_BUSY               Failed due to another operation is on going.
     * @retval  OT_ERROR_NOT_CAPABLE        Requested reset type is not supported by the co-processor.
     */
    otError SendReset(uint8_t aResetType);

    /**
     * Returns the radio Spinel metrics.
     *
     * @returns The radio Spinel metrics.
     */
    const otRadioSpinelMetrics &GetRadioSpinelMetrics(void) const { return mMetrics.GetMetrics(); }

#if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE
    /**
     * Add a calibrated power of the specified channel to the power calibration table.
     *
     * @param[in] aChannel                The radio channel.
     * @param[in] aActualPower            The actual power in 0.01dBm.
     * @param[in] aRawPowerSetting        A pointer to the raw power setting byte array.
     * @param[in] aRawPowerSettingLength  The length of the @p aRawPowerSetting.
     *
     * @retval  OT_ERROR_NONE              Successfully added the calibrated power to the power calibration table.
     * @retval  OT_ERROR_NO_BUFS           No available entry in the power calibration table.
     * @retval  OT_ERROR_INVALID_ARGS      The @p aChannel, @p aActualPower or @p aRawPowerSetting is invalid.
     * @retval  OT_ERROR_NOT_IMPLEMENTED   This feature is not implemented.
     * @retval  OT_ERROR_BUSY              Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT  Failed due to no response received from the transceiver.
     */
    otError AddCalibratedPower(uint8_t        aChannel,
                               int16_t        aActualPower,
                               const uint8_t *aRawPowerSetting,
                               uint16_t       aRawPowerSettingLength);

    /**
     * Clear all calibrated powers from the power calibration table.
     *
     * @retval  OT_ERROR_NONE              Successfully cleared all calibrated powers from the power calibration table.
     * @retval  OT_ERROR_NOT_IMPLEMENTED   This feature is not implemented.
     * @retval  OT_ERROR_BUSY              Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT  Failed due to no response received from the transceiver.
     */
    otError ClearCalibratedPowers(void);

    /**
     * Set the target power for the given channel.
     *
     * @param[in]  aChannel      The radio channel.
     * @param[in]  aTargetPower  The target power in 0.01dBm. Passing `INT16_MAX` will disable this channel.
     *
     * @retval  OT_ERROR_NONE              Successfully set the target power.
     * @retval  OT_ERROR_INVALID_ARGS      The @p aChannel or @p aTargetPower is invalid..
     * @retval  OT_ERROR_NOT_IMPLEMENTED   The feature is not implemented.
     * @retval  OT_ERROR_BUSY              Failed due to another operation is on going.
     * @retval  OT_ERROR_RESPONSE_TIMEOUT  Failed due to no response received from the transceiver.
     */
    otError SetChannelTargetPower(uint8_t aChannel, int16_t aTargetPower);
#endif

#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0
    /**
     * Restore the properties of Radio Co-processor (RCP).
     */
    void RestoreProperties(void);
#endif
#if OPENTHREAD_SPINEL_CONFIG_VENDOR_HOOK_ENABLE
    /**
     * Defines a vendor "set property handler" hook to process vendor spinel properties.
     *
     * The vendor handler should return `OT_ERROR_NOT_FOUND` status if it does not support "set" operation for the
     * given property key. Otherwise, the vendor handler should behave like other property set handlers, i.e., it
     * should first decode the value from the input spinel frame and then perform the corresponding set operation. The
     * handler should not prepare the spinel response and therefore should not write anything to the NCP buffer. The
     * `otError` returned from handler (other than `OT_ERROR_NOT_FOUND`) indicates the error in either parsing of the
     * input or the error of the set operation. In case of a successful "set", `NcpBase` set command handler will call
     * the `VendorGetPropertyHandler()` for the same property key to prepare the response.
     *
     * @param[in] aPropKey  The spinel property key.
     *
     * @returns OT_ERROR_NOT_FOUND if it does not support the given property key, otherwise the error in either parsing
     *          of the input or the "set" operation.
     */
    otError VendorHandleValueIs(spinel_prop_key_t aPropKey);

    /**
     *  A callback type for restoring vendor properties.
     *
     * @param[in] aContext  A pointer to the user context.
     */
    typedef void (*otRadioSpinelVendorRestorePropertiesCallback)(void *aContext);

    /**
     * Registers a callback to restore vendor properties.
     *
     * This function is used to register a callback for vendor properties recovery. When an event which needs to restore
     * properties occurs (such as an unexpected RCP reset), the user can restore the vendor properties via the callback.
     *
     * @param[in] aCallback The callback.
     * @param[in] aContext  A pointer to the user context.
     */
    void SetVendorRestorePropertiesCallback(otRadioSpinelVendorRestorePropertiesCallback aCallback, void *aContext);
#endif // OPENTHREAD_SPINEL_CONFIG_VENDOR_HOOK_ENABLE

#if OPENTHREAD_SPINEL_CONFIG_COMPATIBILITY_ERROR_CALLBACK_ENABLE
    /**
     * A callback type for handling compatibility error of radio spinel.
     *
     * @param[in] aContext  A pointer to the user context.
     */
    typedef void (*otRadioSpinelCompatibilityErrorCallback)(void *aContext);

    /**
     * Registers a callback to handle error of radio spinel.
     *
     * This function is used to register a callback to handle radio spinel compatibility errors. When a radio spinel
     * compatibility error occurs that cannot be resolved by a restart (e.g., RCP version mismatch), the user can
     * handle the error through the callback(such as OTA) instead of letting the program crash directly.
     *
     * @param[in] aCallback The callback.
     * @param[in] aContext  A pointer to the user context.
     */
    void SetCompatibilityErrorCallback(otRadioSpinelCompatibilityErrorCallback aCallback, void *aContext);
#endif

    /**
     * Enables or disables the time synchronization between the host and RCP.
     *
     * @param[in]  aOn  TRUE to turn on the time synchronization, FALSE otherwise.
     */
    void SetTimeSyncState(bool aOn) { mTimeSyncOn = aOn; }

#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0
    /**
     * Enables or disables the RCP restoration feature.
     *
     * @param[in]  aEnabled  TRUE to enable the RCP restoration feature, FALSE otherwise.
     */
    void SetRcpRestorationEnabled(bool aEnabled) { mRcpRestorationEnabled = aEnabled; }
#endif

private:
    enum
    {
        kMaxWaitTime           = 2000, ///< Max time to wait for response in milliseconds.
        kVersionStringSize     = 128,  ///< Max size of version string.
        kCapsBufferSize        = 100,  ///< Max buffer size used to store `SPINEL_PROP_CAPS` value.
        kChannelMaskBufferSize = 32,   ///< Max buffer size used to store `SPINEL_PROP_PHY_CHAN_SUPPORTED` value.
    };

    enum State
    {
        kStateDisabled,     ///< Radio is disabled.
        kStateSleep,        ///< Radio is sleep.
        kStateReceive,      ///< Radio is in receive mode.
        kStateTransmitting, ///< Frame passed to radio for transmission, waiting for done event from radio.
        kStateTransmitDone, ///< Radio indicated frame transmission is done.
    };

    static constexpr uint32_t kUsPerMs  = 1000;                 ///< Microseconds per millisecond.
    static constexpr uint32_t kMsPerSec = 1000;                 ///< Milliseconds per second.
    static constexpr uint32_t kUsPerSec = kUsPerMs * kMsPerSec; ///< Microseconds per second.
    static constexpr uint64_t kTxWaitUs =
        OPENTHREAD_SPINEL_CONFIG_RCP_TX_WAIT_TIME_SECS *
        kUsPerSec; ///< Maximum time of waiting for `TransmitDone` event, in microseconds.

    typedef otError (RadioSpinel::*ResponseHandler)(const uint8_t *aBuffer, uint16_t aLength);

    SpinelDriver &GetSpinelDriver(void) const;

    otError CheckSpinelVersion(void);
    otError CheckRadioCapabilities(otRadioCaps aRequiredRadioCaps);
    otError CheckRcpApiVersion(bool aSupportsRcpApiVersion, bool aSupportsRcpMinHostApiVersion);
    void    InitializeCaps(bool &aSupportsRcpApiVersion, bool &aSupportsRcpMinHostApiVersion);

    /**
     * Triggers a state transfer of the state machine.
     */
    void ProcessRadioStateMachine(void);

    /**
     * Processes the frame queue.
     */
    void ProcessFrameQueue(void);

    spinel_tid_t GetNextTid(void);
    void         FreeTid(spinel_tid_t tid) { mCmdTidsInUse &= ~(1 << tid); }

    otError RequestV(uint32_t aCommand, spinel_prop_key_t aKey, const char *aFormat, va_list aArgs);
    otError Request(uint32_t aCommand, spinel_prop_key_t aKey, const char *aFormat, ...);
    otError RequestWithPropertyFormat(const char       *aPropertyFormat,
                                      uint32_t          aCommand,
                                      spinel_prop_key_t aKey,
                                      const char       *aFormat,
                                      ...);
    otError RequestWithPropertyFormatV(const char       *aPropertyFormat,
                                       uint32_t          aCommand,
                                       spinel_prop_key_t aKey,
                                       const char       *aFormat,
                                       va_list           aArgs);
    otError RequestWithExpectedCommandV(uint32_t          aExpectedCommand,
                                        uint32_t          aCommand,
                                        spinel_prop_key_t aKey,
                                        const char       *aFormat,
                                        va_list           aArgs);
    otError WaitResponse(bool aHandleRcpTimeout = true);
    otError ParseRadioFrame(otRadioFrame &aFrame, const uint8_t *aBuffer, uint16_t aLength, spinel_ssize_t &aUnpacked);

    /**
     * Returns if the property changed event is safe to be handled now.
     *
     * If a property handler will go up to core stack, it may cause reentrant issue of `Hdlc::Decode()` and
     * `WaitResponse()`.
     *
     * @param[in] aKey The identifier of the property.
     *
     * @returns Whether this property is safe to be handled now.
     */
    bool IsSafeToHandleNow(spinel_prop_key_t aKey) const
    {
        return !(aKey == SPINEL_PROP_STREAM_RAW || aKey == SPINEL_PROP_MAC_ENERGY_SCAN_RESULT);
    }

    void HandleNotification(const uint8_t *aFrame, uint16_t aLength, bool &aShouldSaveFrame);
    void HandleNotification(const uint8_t *aFrame, uint16_t aLength);
    void HandleValueIs(spinel_prop_key_t aKey, const uint8_t *aBuffer, uint16_t aLength);

    void HandleResponse(const uint8_t *aBuffer, uint16_t aLength);
    void HandleTransmitDone(uint32_t aCommand, spinel_prop_key_t aKey, const uint8_t *aBuffer, uint16_t aLength);
    void HandleWaitingResponse(uint32_t aCommand, spinel_prop_key_t aKey, const uint8_t *aBuffer, uint16_t aLength);

    void RadioReceive(void);

    void TransmitDone(otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError);

    void CalcRcpTimeOffset(void);

    void HandleRcpUnexpectedReset(spinel_status_t aStatus);
    void HandleRcpTimeout(void);
    void RecoverFromRcpFailure(void);

    static void HandleReceivedFrame(const uint8_t *aFrame,
                                    uint16_t       aLength,
                                    uint8_t        aHeader,
                                    bool          &aSave,
                                    void          *aContext);
    void        HandleReceivedFrame(const uint8_t *aFrame, uint16_t aLength, uint8_t aHeader, bool &aShouldSaveFrame);
    static void HandleSavedFrame(const uint8_t *aFrame, uint16_t aLength, void *aContext);
    void        HandleSavedFrame(const uint8_t *aFrame, uint16_t aLength);

    void UpdateParseErrorCount(otError aError)
    {
        if (aError == OT_ERROR_PARSE)
        {
            mMetrics.IncrementSpinelParseErrorCount();
        }
    }

    otError SetMacKey(uint8_t         aKeyIdMode,
                      uint8_t         aKeyId,
                      const otMacKey &aPrevKey,
                      const otMacKey &aCurrKey,
                      const otMacKey &NextKey);
#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
    static otError ReadMacKey(const otMacKeyMaterial &aKeyMaterial, otMacKey &aKey);
#endif

#if OPENTHREAD_CONFIG_DIAG_ENABLE
    void PlatDiagOutput(const char *aFormat, ...);
#endif

    void HandleCompatibilityError(void);

    typedef otRadioSpinelMetrics Metrics;

    class MetricsTracker
    {
    public:
        explicit MetricsTracker(RadioSpinelCallbacks &aCallbacks)
            : mCallbacks(aCallbacks)
        {
            memset(&mMetrics, 0, sizeof(mMetrics));
        }

        void Init(void);
        void IncrementRcpTimeoutCount(void) { IncrementCount(kTypeTimeoutCount); }
        void IncrementRcpUnexpectedResetCount(void) { IncrementCount(kTypeUnexpectResetCount); }
        void IncrementRcpRestorationCount(void) { IncrementCount(kTypeRestorationCount); }
        void IncrementSpinelParseErrorCount(void) { IncrementCount(kTypeSpinelParseErrorCount); }

        const Metrics &GetMetrics(void) const { return mMetrics; }

    private:
        enum MetricType : uint8_t
        {
            kTypeTimeoutCount,
            kTypeUnexpectResetCount,
            kTypeRestorationCount,
            kTypeSpinelParseErrorCount,
        };

        void RestoreMetrics(void);
        void SaveMetrics(void);
        void IncrementCount(MetricType aType);

        RadioSpinelCallbacks &mCallbacks;
        Metrics               mMetrics;
    };

    otInstance *mInstance;

    RadioSpinelCallbacks mCallbacks; ///< Callbacks for notifications of higher layer.

    uint16_t          mCmdTidsInUse;    ///< Used transaction ids.
    spinel_tid_t      mCmdNextTid;      ///< Next available transaction id.
    spinel_tid_t      mTxRadioTid;      ///< The transaction id used to send a radio frame.
    spinel_tid_t      mWaitingTid;      ///< The transaction id of current transaction.
    spinel_prop_key_t mWaitingKey;      ///< The property key of current transaction.
    const char       *mPropertyFormat;  ///< The spinel property format of current transaction.
    va_list           mPropertyArgs;    ///< The arguments pack or unpack spinel property of current transaction.
    uint32_t          mExpectedCommand; ///< Expected response command of current transaction.
    otError           mError;           ///< The result of current transaction.
    uint8_t           mRxPsdu[OT_RADIO_FRAME_MAX_SIZE];
    uint8_t           mTxPsdu[OT_RADIO_FRAME_MAX_SIZE];
    uint8_t           mAckPsdu[OT_RADIO_FRAME_MAX_SIZE];
    otRadioFrame      mRxRadioFrame;
    otRadioFrame      mTxRadioFrame;
    otRadioFrame      mAckRadioFrame;
    otRadioFrame     *mTransmitFrame; ///< Points to the frame to send

#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT && OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
    otRadioIeInfo mTxIeInfo;
#endif

    otExtAddress        mExtendedAddress;
    uint16_t            mShortAddress;
    uint16_t            mPanId;
    uint8_t             mChannel;
    int8_t              mRxSensitivity;
    otError             mTxError;
    static otExtAddress sIeeeEui64;
    static otRadioCaps  sRadioCaps;
    uint32_t            mBusLatency;

    State mState;
    bool  mIsPromiscuous : 1; ///< Promiscuous mode.
    bool  mRxOnWhenIdle : 1;  ///< RxOnWhenIdle mode.
    bool  mIsTimeSynced : 1;  ///< Host has calculated the time difference between host and RCP.

    static bool sSupportsLogStream; ///< RCP supports `LOG_STREAM` property with OpenThread log meta-data format.
    static bool sSupportsResetToBootloader; ///< RCP supports resetting into bootloader mode.
    static bool sSupportsLogCrashDump;      ///< RCP supports logging a crash dump.

#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0

    enum
    {
        kRcpFailureNone,
        kRcpFailureTimeout,
        kRcpFailureUnexpectedReset,
    };

    bool    mResetRadioOnStartup : 1; ///< Whether should send reset command when init.
    int16_t mRcpFailureCount;         ///< Count of consecutive RCP failures.
    uint8_t mRcpFailure : 2;          ///< RCP failure reason, should recover and retry operation.

    // Properties set by core.
    uint8_t  mKeyIdMode;
    uint8_t  mKeyId;
    otMacKey mPrevKey;
    otMacKey mCurrKey;
    otMacKey mNextKey;
    static_assert(OPENTHREAD_SPINEL_CONFIG_MAX_SRC_MATCH_ENTRIES >= OPENTHREAD_CONFIG_MLE_MAX_CHILDREN,
                  "SPINEL_CONFIG_MAX_SRC_MATCH_ENTRIES is not large enough to cover MLE_MAX_CHILDREN");
    uint16_t     mSrcMatchShortEntries[OPENTHREAD_SPINEL_CONFIG_MAX_SRC_MATCH_ENTRIES];
    int16_t      mSrcMatchShortEntryCount;
    otExtAddress mSrcMatchExtEntries[OPENTHREAD_SPINEL_CONFIG_MAX_SRC_MATCH_ENTRIES];
    int16_t      mSrcMatchExtEntryCount;
    uint8_t      mScanChannel;
    uint16_t     mScanDuration;
    int8_t       mCcaEnergyDetectThreshold;
    int8_t       mTransmitPower;
    int8_t       mFemLnaGain;
    bool         mCoexEnabled : 1;
    bool         mSrcMatchEnabled : 1;
    bool         mRcpRestorationEnabled : 1;

    bool mMacKeySet : 1;                   ///< Whether MAC key has been set.
    bool mCcaEnergyDetectThresholdSet : 1; ///< Whether CCA energy detect threshold has been set.
    bool mTransmitPowerSet : 1;            ///< Whether transmit power has been set.
    bool mCoexEnabledSet : 1;              ///< Whether coex enabled has been set.
    bool mFemLnaGainSet : 1;               ///< Whether FEM LNA gain has been set.
    bool mEnergyScanning : 1;              ///< If fails while scanning, restarts scanning.
    bool mMacFrameCounterSet : 1;          ///< Whether the MAC frame counter has been set.
    bool mSrcMatchSet : 1;                 ///< Whether the source match feature has been set.

#endif // OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0

#if OPENTHREAD_CONFIG_DIAG_ENABLE
    bool                     mDiagMode;
    otPlatDiagOutputCallback mOutputCallback;
    void                    *mOutputContext;
#endif

    uint64_t mTxRadioEndUs;
    uint64_t mRadioTimeRecalcStart; ///< When to recalculate RCP time offset.
    uint64_t mRadioTimeOffset;      ///< Time difference with estimated RCP time minus host time.

    MaxPowerTable  mMaxPowerTable;
    MetricsTracker mMetrics;

#if OPENTHREAD_SPINEL_CONFIG_VENDOR_HOOK_ENABLE
    otRadioSpinelVendorRestorePropertiesCallback mVendorRestorePropertiesCallback;
    void                                        *mVendorRestorePropertiesContext;
#endif

#if OPENTHREAD_SPINEL_CONFIG_COMPATIBILITY_ERROR_CALLBACK_ENABLE
    otRadioSpinelCompatibilityErrorCallback mCompatibilityErrorCallback;
    void                                   *mCompatibilityErrorContext;
#endif

    bool mTimeSyncEnabled : 1;
    bool mTimeSyncOn : 1;

    SpinelDriver *mSpinelDriver;
};

} // namespace Spinel
} // namespace ot

#endif // RADIO_SPINEL_HPP_
